package com.lhl.api.exception;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.lhl.fw.core.base.ResultDTO;
import com.lhl.fw.core.exception.BizException;
import com.lhl.fw.core.exception.CommonErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @className: GlobalDefaultExceptionHandler
 * @description: 基础全局异常处理
 * @author: king
 * @date: 2020-07-06 8:24
 **/
@RestControllerAdvice
@Slf4j
public class GlobalDefaultExceptionHandler {
    /**
     * NoHandlerFoundException 404 异常处理
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResultDTO handlerNoHandlerFoundException(NoHandlerFoundException exception) {
        outPutErrorWarn(NoHandlerFoundException.class, CommonErrorCode.NOT_FOUND, exception);
        return ResultDTO.getFailDto()
                .code(CommonErrorCode.NOT_FOUND.getCode())
                .msg(CommonErrorCode.NOT_FOUND.getMessage()).build();
    }


    /**
     * Exception 服务器异常 异常处理
     */
    @ExceptionHandler(value = Exception.class)
    public ResultDTO handlerException(Exception e) {
        outPutError(Exception.class, CommonErrorCode.SYS_ERROR, e);
        return ResultDTO.getFailDto()
                .code(CommonErrorCode.SYS_ERROR.getCode())
                .msg(CommonErrorCode.SYS_ERROR.getMessage()).build();
    }

    /**
     * BusinessException 类捕获
     */
    @ExceptionHandler(value = BizException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultDTO handlerBusinessException(BizException e) {
        outPutError(BizException.class, CommonErrorCode.FAILED, e);
        return ResultDTO.getFailDto()
                .msg(e.getMessage()).build();
    }

    /**
     * IllegalArgumentException 使用断言Assert捕获异常信息
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultDTO handlerIllegalArgumentException(IllegalArgumentException e) {
        return ResultDTO.getFailDto()
                .msg(e.getMessage()).build();
    }

    /**
     * HttpMessageNotReadableException 参数错误异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResultDTO handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        outPutError(HttpMessageNotReadableException.class, CommonErrorCode.INVALID_REQUEST_PARAM_ERROR, e);
        String msg = StrUtil.format("{} : 错误详情( {} )", CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getMessage(),
                e.getRootCause().getMessage());
        return ResultDTO.getFailDto()
                .msg(msg).build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        return getBindResultDTO(bindingResult);
    }

    /**
     * @param: ex
     * @description: 绑定参数异常
     * @return: com.lhl.fw.core.base.ResultDTO
     * @author: king
     * @date: 2020-11-12
     */
    @ExceptionHandler(BindException.class)
    public ResultDTO handleBindException(BindException ex) {
        BindingResult bindingResult = ex.getBindingResult();
        return getBindResultDTO(bindingResult);
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResultDTO handleConstraintViolationException(ConstraintViolationException ex) {
        String smg = "";
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (log.isDebugEnabled()) {
            for (ConstraintViolation error : constraintViolations) {
                log.error("{} -> {}", error.getPropertyPath(), error.getMessageTemplate());
                smg = error.getMessageTemplate();
            }
        }

        if (constraintViolations.isEmpty()) {
            log.error("validExceptionHandler error fieldErrors is empty");
            return ResultDTO.getFailDto()
                    .code(CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getCode())
                    .msg(smg).build();
        } else {
            Map<String, String> errors = MapUtil.newHashMap();
            constraintViolations.forEach(error -> {
                errors.put(error.getPropertyPath().toString(), error.getMessageTemplate());
            });
            return ResultDTO.getFailDto().code(CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getCode())
                    .errors(errors).build();
        }

    }

    private ResultDTO getBindResultDTO(BindingResult bindingResult) {
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        if (log.isDebugEnabled()) {
            for (FieldError error : fieldErrors) {
                log.error("{} -> {}", error.getField(), error.getDefaultMessage());
            }
        }

        if (fieldErrors.isEmpty()) {
            log.error("validExceptionHandler error fieldErrors is empty");
            return ResultDTO.getFailDto()
                    .code(CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getCode()).build();
        } else {
            Map<String, String> errors = new HashMap<>();
            fieldErrors.forEach(
                    (error) -> {
                        String fieldName = ((FieldError) error).getField();
                        String errorMessage = error.getDefaultMessage();
                        errors.put(fieldName, errorMessage);
                    });
            return ResultDTO.getFailDto()
                    .code(CommonErrorCode.INVALID_REQUEST_PARAM_ERROR.getCode())
                    .errors(errors).build();
        }
    }

    public void outPutError(Class errorType, Enum secondaryErrorType, Throwable throwable) {
        log.error("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage(),
                throwable);
    }

    public void outPutErrorWarn(Class errorType, Enum secondaryErrorType, Throwable throwable) {
        log.warn("[{}] {}: {}", errorType.getSimpleName(), secondaryErrorType, throwable.getMessage());
    }
}
